﻿using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Intentions.Extensibility.Menu;
using JetBrains.ReSharper.Intentions.Util;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CodeStyle;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Parsing;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Text;
using JetBrains.TextControl;
using JetBrains.Util;
using MugenMvvmToolkit.ReSharper.Infrastructure;

namespace MugenMvvmToolkit.ReSharper.ViewModels.ContextActions
{
    [ContextAction(Name = "ImplementCloseableInterfaceAction", Group = "C#")]
    public class ImplementCloseableInterfaceAction : ContextActionImplBase
    {
        #region Fields

        private readonly LanguageService _languageService;
        private IClassDeclaration _currentClassDeclaration;

        #endregion

        #region Constructors

        public ImplementCloseableInterfaceAction([NotNull] ICSharpContextActionDataProvider provider)
            : base(provider)
        {
            _languageService = Provider.PsiFile.Language.LanguageService();
        }

        #endregion

        #region Overrides of ContextActionImplBase

        public override IEnumerable<IntentionAction> CreateBulbItems()
        {
            return new IBulbAction[]
            {
                new DelegateBulbAction((solution, indicator) => Execute(false, false, CodeTemplateConstants.CloseableViewModelAssignField),
                    UiResources.ImplementCloseableViewModel),
                new DelegateBulbAction((solution, indicator) => Execute(true, false,CodeTemplateConstants.CloseableViewModelAssignFieldWithCanClose),
                    UiResources.ImplementCloseableViewModelWithCanClose),
                new DelegateBulbAction((solution, indicator) => Execute(true, true, CodeTemplateConstants.CloseableViewModelAssignFieldWithProtectedMethods),
                    UiResources.ImplementCloseableViewModelExt)
            }.ToContextAction();
        }

        /// <summary>
        ///     Check if this action is available at the constructed context.
        ///     Actions could store precalculated info in <paramref name="cache" /> to share it between different actions
        /// </summary>
        /// <returns>
        ///     true if this bulb action is available, false otherwise.
        /// </returns>
        public override bool IsAvailable(IUserDataHolder cache)
        {
            if (Provider.SelectedElement == null || _languageService == null || !TypeProvider.NotifyViewModelClosingInterface.IsResolved)
                return false;
            _currentClassDeclaration = Provider.SelectedElement.Parent as IClassDeclaration;
            return _currentClassDeclaration != null &&
                   !_currentClassDeclaration.IsOfType(TypeProvider.CloseableViewModelInterface) &&
                   _currentClassDeclaration.IsOfType(TypeProvider.ViewModelInterface);
        }

        #endregion

        #region Methods

        private Action<ITextControl> Execute(bool withCanExecuteMethod, bool withProtectedMethods, string initStatement)
        {
            _currentClassDeclaration.AddSuperInterface(TypeProvider.NotifyViewModelClosingInterface, false);

            var field = (IFieldDeclaration)Provider
                .ElementFactory
                .CreateTypeMemberDeclaration(CodeTemplateConstants.CloseableViewModelFacadeField, TypeProvider.CloseableViewModelFacade);
            field = _currentClassDeclaration.AddClassMemberDeclaration(field);
            ICSharpStatement initializeFieldMethod = Provider
                .ElementFactory
                .CreateStatement(string.Format("{0} = {1}", field.DeclaredName, initStatement));
            if (_currentClassDeclaration.ConstructorDeclarations.IsEmpty)
            {
                IConstructorDeclaration constructorDeclaration = Provider.ElementFactory.CreateConstructorDeclaration();
                _currentClassDeclaration.AddClassMemberDeclaration(constructorDeclaration);
            }

            foreach (IConstructorDeclaration constructorDeclaration in
                    _currentClassDeclaration.ConstructorDeclarations.Where(declaration => declaration.Body != null))
            {
                if (constructorDeclaration.Initializer != null &&
                    constructorDeclaration.Initializer.Kind == ConstructorInitializerKind.THIS)
                    continue;
                ICSharpStatement sharpStatement = constructorDeclaration.Body.AddStatementAfter(initializeFieldMethod, null);
                ContextActionUtils.FormatWithDefaultProfile(sharpStatement);
            }

            ITreeNode anchor = _currentClassDeclaration.ConstructorDeclarations.Last();

            JetTuple<IStartRegion, IEndRegion> regionDirective = Provider
                .ElementFactory
                .CreateRegionDirective(UiResources.RegionCloseableViewModel);
            ITreeNode firstNode = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, regionDirective.A);
            anchor = firstNode;

            //TODO Throws an 'JetBrains.ReSharper.Psi.ElementFactoryException' Additional information: Cannot find ITypeUsageNode or IDeclaredTypeUsageNode for element '0'
            //statement = Provider.ElementFactory
            //        .CreateTypeMemberDeclaration(CodeTemplateConstants.CloseableViewModelClosingEvent, EventProvider.CloseableViewModelInterfaceClosing.Type);

            //Properties
            var statement = (ITreeNode)Provider.ElementFactory
                .CreateTypeMemberDeclaration(CodeTemplateConstants.CloseableViewModelCloseCommand,
                    TypeProvider.CommandInterface.GetTypeElement());
            anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, statement);

            //Interface methods
            statement = CreateParser(string.Format(CodeTemplateConstants.CloseableViewModelCloseMethod, GetTaskName()))
                .ParseMemberDeclaration();
            SandBox.CreateSandBoxFor(statement, Provider.PsiModule, CSharpLanguage.Instance);
            anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, statement);
            if (MethodProvider.CloseableViewModelInterfaceClose != null)
                Extensions.AddTypeNamespace(Provider.ElementFactory, MethodProvider.CloseableViewModelInterfaceClose.ReturnType, anchor);

            //Events
            statement = CreateParser(CodeTemplateConstants.CloseableViewModelClosingEvent).ParseMemberDeclaration();
            SandBox.CreateSandBoxFor(statement, Provider.PsiModule, CSharpLanguage.Instance);
            anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, statement);
            if (EventProvider.CloseableViewModelInterfaceClosing != null)
                Extensions.AddTypeNamespace(Provider.ElementFactory, EventProvider.CloseableViewModelInterfaceClosing.Type, anchor);

            statement = CreateParser(CodeTemplateConstants.CloseableViewModelClosedEvent).ParseMemberDeclaration();
            SandBox.CreateSandBoxFor(statement, Provider.PsiModule, CSharpLanguage.Instance);
            anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, statement);
            if (EventProvider.CloseableViewModelInterfaceClosed != null)
                Extensions.AddTypeNamespace(Provider.ElementFactory, EventProvider.CloseableViewModelInterfaceClosed.Type, anchor);

            //Protected members
            if (withCanExecuteMethod)
            {
                statement = Provider.ElementFactory.CreateTypeMemberDeclaration(
                    CodeTemplateConstants.CloseableViewModelCanCloseMethod);
                anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, statement);
            }
            if (withProtectedMethods)
            {
                statement = CreateParser(string.Format(CodeTemplateConstants.CloseableViewModelOnClosingMethod, GetTaskName())).ParseMemberDeclaration();
                SandBox.CreateSandBoxFor(statement, Provider.PsiModule, CSharpLanguage.Instance);
                anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, statement);

                statement = Provider.ElementFactory.CreateTypeMemberDeclaration(
                    CodeTemplateConstants.CloseableViewModelOnClosedMethod);
                anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, statement);
                Extensions.AddTypeNamespace(Provider.ElementFactory, TypeProvider.MvvmUtils, anchor);
            }

            anchor = ModificationUtil.AddChildAfter(_currentClassDeclaration, anchor, regionDirective.B);

            if (_languageService.CodeFormatter != null)
                _languageService.CodeFormatter.Format(firstNode, anchor);
            return null;
        }

        private string GetTaskName()
        {
            return TypeProvider.IsSupportedTask ? "Task<bool>" : "IOperationResult<bool>";
        }

        private ICSharpParser CreateParser(string code)
        {
            return (ICSharpParser)_languageService
                .CreateParser(_languageService
                    .GetPrimaryLexerFactory()
                    .CreateLexer(new StringBuffer(code)), Provider.PsiModule, Provider.SourceFile);
        }

        #endregion

    }
}